﻿using System;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Globalization;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using System.Windows.Forms;
using Microsoft.VisualStudio.Text.Tagging;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Formatting;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio.Editor;
using System.Collections.Generic;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.VCCodeModel;

using System.Text;
using System.Drawing;
using EnvDTE;
using EnvDTE80;

using System.IO;
using System.Reflection;

namespace FunctionBar
{


    // so, the vast majority of documentation on custom dropdown
    // bars assumes that you're implementing your own custom
    // editor window, to be used with some new language. 
    // 
    // my sense is thus that the "right" way to replace the
    // standard C/C++ dropdown bar might be to create a new
    // "language" that would override the base C/C++ definition,
    // along with a new editor that would override the base C/C++
    // editor definition.  you'd then inherit from the visual
    // studio standard types for everything but the handful of
    // functions involved with creating the dropdown bar.
    //
    // however, i had no idea how i'd go about inheriting from
    // those standard classes.  i did, however, have enough
    // experience with text view events to figure out the following 
    // fairly ugly yet functional hack, which involves creating a
    // running document table event listener and then using the
    // OnBeforeDocumentWindowShow event to ensure that my own
    // toolbar is always shown any time C/C++ files are displayed.
    //
    // just to complicate things a little more, i also register
    // this class for idle time, so that i can update the currently
    // selected function based on the textview's caret position.
    // 

    //! Known Bugs
    //
    // occasionally, i've noticed that some other CodeWindow (like
    // a C# window) can end up with a lua dropdown bar.  however,
    // the bug is rare, and i've recently made changes to the way
    // lua check works that should probably eliminate it.
    //

    [Export(typeof(IVsTextViewCreationListener))]
    [ContentType("text")]
    [TextViewRole(PredefinedTextViewRoles.Interactive)]
    public class CreationListener : IVsTextViewCreationListener, IVsRunningDocTableEvents, IOleComponent
    {
    

        [Import]
        internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }

        // this gets us access to the DTE, if we need it.
        [Import]
        internal SVsServiceProvider ServiceProvider = null;


        // various other factories i'm not using atm.
        /*
        [Import]
        internal IClassifierAggregatorService AggregatorFactory;
        */
        /*
        [Import]
        internal IVsContainedLanguageFactory languageFactory { get; set; }
        */
        //[Import]
        //internal IEditorOperationsFactoryService opFactory { get; set; }


         

        uint rdt_cookie = 0;

        Dictionary<IVsTextView, FunBar> bars = new Dictionary<IVsTextView, FunBar>();


        public void VsTextViewCreated(IVsTextView textViewAdapter)
        {
            
            IVsRunningDocumentTable rdt = ServiceProvider.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
            if (rdt != null && rdt_cookie==0)
            {
                rdt.AdviseRunningDocTableEvents((IVsRunningDocTableEvents)this, out rdt_cookie);
                
                RegisterForIdleTime();
            }

        }

        uint componentID=0;
        private void RegisterForIdleTime()
        {
            IOleComponentManager mgr = ServiceProvider.GetService(typeof(SOleComponentManager)) as IOleComponentManager;
            if (componentID == 0 && mgr != null)
            {
                OLECRINFO[] crinfo = new OLECRINFO[1];
                crinfo[0].cbSize = (uint)Marshal.SizeOf(typeof(OLECRINFO));
                crinfo[0].grfcrf = (uint)_OLECRF.olecrfNeedIdleTime |
                                              (uint)_OLECRF.olecrfNeedPeriodicIdleTime;
                crinfo[0].grfcadvf = (uint)_OLECADVF.olecadvfModal |
                                              (uint)_OLECADVF.olecadvfRedrawOff |
                                              (uint)_OLECADVF.olecadvfWarningsOff;
                crinfo[0].uIdleTimeInterval = 1000;
                int hr = mgr.FRegisterComponent(this, crinfo, out componentID);
            }
        }

        /*
        private void findCodeWindow(Window window)
        {
            ServiceProvider sp = new ServiceProvider((IOleServiceProvider)window.DTE);
            IVsUIShell shell = (IVsUIShell)sp.GetService(typeof(SVsUIShell));
            IEnumWindowFrames frames = null;
            IVsWindowFrame[] frameArray = new IVsWindowFrame[window.DTE.Windows.Count];
            shell.GetDocumentWindowEnum(out frames);
            frames.Next(Convert.ToUInt32(window.DTE.Windows.Count), frameArray, out count);
            object value;
 
            for (int i = 0; i < count; i++)
            {
            frameArray[i].GetProperty((int)__VSFPROPID.VSFPROPID_Caption, out value);
            if (value.ToString() == window.Caption)
            {
                frameArray[i].GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out value);
                codeWindow = value as IVsCodeWindow;
                break;
            }
            }
        }
        */

        //
        // while lua doesn't have a real language service, it looks
        // like i can still get a IVsCodeWindow for lua files, and thus
        // add dropdown bars for them.  given that i don't have a
        // language service, i just assume that anything with a .lua
        // extension is a lua file..
        //
        bool lua_check(IVsCodeWindow codeWindow)
        {

            IVsDropdownBar current;
            IVsDropdownBarManager manager = codeWindow as IVsDropdownBarManager;

            IVsTextView view = null;
            codeWindow.GetPrimaryView(out view);
            if (view==null) return true;

            // use the view's buffer to figure out the file name, from
            // which we infer whether or not we're looking at a lua file.
            IVsTextLines buffer;
            view.GetBuffer(out buffer);
            IVsUserData data = buffer as IVsUserData;
            if (data == null) return false;
            object nameobj;
            data.GetData(typeof(IVsUserData).GUID, out nameobj);
            if (nameobj == null) return false;
            string fname = nameobj as string;
            if(!fname.ToLower().EndsWith(".lua")) return false;

            int rval = manager.GetDropdownBar(out current);
            if (rval == VSConstants.S_OK)
            {
                IVsDropdownBarClient client = null;
                current.GetClient(out client);
                LuaBar cast = client as LuaBar;

                if (cast == null) manager.RemoveDropdownBar();
                else
                {
                    cast.vsTextView = view;
                    bars[view] = cast;
                    return true;
                }
            }

            LuaBar bar = new LuaBar();
            bar.ServiceProvider = ServiceProvider;
            bar.EditorAdaptersFactoryService = EditorAdaptersFactoryService;
            bar.vsTextView = view;
            bars[view] = bar;

            manager.AddDropdownBar(1, bar);
            bar.refresh_if_needed();

            return true;

        }

        public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
        {

            IntPtr pCodeWindow;
            Guid riid2 = typeof(IVsCodeWindow).GUID;
            pFrame.QueryViewInterface(ref riid2, out pCodeWindow);
            if (pCodeWindow.ToInt32() == 0)
            {
                return 0;
            }
            IVsCodeWindow codeWindow = Marshal.GetObjectForIUnknown(pCodeWindow) as IVsCodeWindow;

            if (codeWindow != null)
            {


                if (lua_check(codeWindow)) return 0;

                IVsTextLines lines = null;
                IVsTextView vsTextView = null;

                codeWindow.GetPrimaryView(out vsTextView);

                if (vsTextView == null) return 0;

                vsTextView.GetBuffer(out lines);


                // jump through some painful hoops in hopes of figuring out the
                // name of the language
                Guid guidLangSvc;
                lines.GetLanguageServiceID(out guidLangSvc); // got a Guid of currently used language service.

                Microsoft.VisualStudio.OLE.Interop.IServiceProvider provider = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)ServiceProvider.GetService(typeof(Microsoft.VisualStudio.OLE.Interop.IServiceProvider));

                Guid riid = VSConstants.IID_IUnknown;
                Guid iid = typeof(IVsLanguageInfo).GUID;
                IntPtr ppvObject = IntPtr.Zero;
                int result = provider.QueryService(ref guidLangSvc, ref iid, out ppvObject);
                object langSvc = Marshal.GetObjectForIUnknown(ppvObject);
                IVsLanguageInfo li = langSvc as IVsLanguageInfo;
                if (ppvObject != IntPtr.Zero)
                    Marshal.Release(ppvObject);


                string name;
                li.GetLanguageName(out name);

                if (name == "C/C++")
                {
                    IVsDropdownBar current;

                    IVsDropdownBarManager manager = codeWindow as IVsDropdownBarManager;


                    int rval = manager.GetDropdownBar(out current);
                    if (rval == VSConstants.S_OK)
                    {
                        IVsDropdownBarClient client = null;
                        current.GetClient(out client);
                        CBar cast = client as CBar;

                        if (cast == null) manager.RemoveDropdownBar();
                        else
                        {
                            cast.vsTextView = vsTextView;
                            bars[vsTextView] = cast;
                            return 0;
                        }
                    }

                    CBar bar = new CBar();
                    bar.ServiceProvider = ServiceProvider;
                    bar.EditorAdaptersFactoryService = EditorAdaptersFactoryService;
                    bar.vsTextView = vsTextView;
                    bars[vsTextView] = bar;

                    manager.AddDropdownBar(1, bar);
                    bar.refresh_if_needed();

                    return 0;
                }

                // got something.
                return 0;
            }
          

            return 0;
        }

        
        public int FDoIdle(uint grfidlef)
        {
            IVsTextView vsTextView;
            IVsTextManager tm = ServiceProvider.GetService(typeof(SVsTextManager)) as IVsTextManager;
            int hr = tm.GetActiveView(1, null, out vsTextView);
            if (vsTextView == null) return 0;

            if (bars.ContainsKey(vsTextView))
            {
                FunBar bar = bars[vsTextView];

                bar.refresh_if_needed();
                bar.update_caret_pos();
            }

            return 0;
        }

        //
        // various IVsRunningDocTableEvents hooks we're not using
        //
        public int OnAfterAttributeChange(uint docCookie, uint grfAttribs)
        {
            return 0;
        }

        public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame)
        {
            return 0;
        }

        public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
        {
            return 0;
        }

        public int OnAfterSave(uint docCookie)
        {
            return 0;
        }



        public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
        {
            return 0;
        }



        //
        // various IOleComponent hooks we're not using
        //
        public int FContinueMessageLoop(uint uReason, IntPtr pvLoopData, MSG[] pMsgPeeked)
        {
            return 1;
        }

        public int FPreTranslateMessage(MSG[] pMsg)
        {
            return 0;
        }

        public int FQueryTerminate(int fPromptUser)
        {
            return 1;
        }

        public int FReserved1(uint dwReserved, uint message, IntPtr wParam, IntPtr lParam)
        {
            return 1;
        }

        public IntPtr HwndGetWindow(uint dwWhich, uint dwReserved)
        {
            return IntPtr.Zero;
        }


        public void OnActivationChange(IOleComponent pic, int fSameComponent, OLECRINFO[] pcrinfo, int fHostIsActivating, OLECHOSTINFO[] pchostinfo, uint dwReserved)
        {
        }

        public void OnAppActivate(int fActive, uint dwOtherThreadID)
        {
        }

        public void OnEnterState(uint uStateID, int fEnter)
        {
        }

        public void OnLoseActivation()
        {
        }

        public void Terminate()
        {
        }
         
    }

}
